"""Support for Honeywell Lyric climate platform."""
from __future__ import annotations

import logging
from time import localtime, strftime, time

from aiolyric.objects.device import LyricDevice
from aiolyric.objects.location import LyricLocation
import voluptuous as vol

from homeassistant.components.climate import ClimateEntity
from homeassistant.components.climate.const import (
    ATTR_TARGET_TEMP_HIGH,
    ATTR_TARGET_TEMP_LOW,
    CURRENT_HVAC_COOL,
    CURRENT_HVAC_HEAT,
    CURRENT_HVAC_IDLE,
    CURRENT_HVAC_OFF,
    HVAC_MODE_COOL,
    HVAC_MODE_HEAT,
    HVAC_MODE_HEAT_COOL,
    HVAC_MODE_OFF,
    SUPPORT_PRESET_MODE,
    SUPPORT_TARGET_TEMPERATURE,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_TEMPERATURE
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_platform
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator

from . import LyricDeviceEntity
from .const import (
    DOMAIN,
    LYRIC_EXCEPTIONS,
    PRESET_HOLD_UNTIL,
    PRESET_NO_HOLD,
    PRESET_PERMANENT_HOLD,
    PRESET_TEMPORARY_HOLD,
    PRESET_VACATION_HOLD,
)

_LOGGER = logging.getLogger(__name__)

SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE

LYRIC_HVAC_ACTION_OFF = "EquipmentOff"
LYRIC_HVAC_ACTION_HEAT = "Heat"
LYRIC_HVAC_ACTION_COOL = "Cool"

LYRIC_HVAC_MODE_OFF = "Off"
LYRIC_HVAC_MODE_HEAT = "Heat"
LYRIC_HVAC_MODE_COOL = "Cool"
LYRIC_HVAC_MODE_HEAT_COOL = "Auto"

LYRIC_HVAC_MODES = {
    HVAC_MODE_OFF: LYRIC_HVAC_MODE_OFF,
    HVAC_MODE_HEAT: LYRIC_HVAC_MODE_HEAT,
    HVAC_MODE_COOL: LYRIC_HVAC_MODE_COOL,
    HVAC_MODE_HEAT_COOL: LYRIC_HVAC_MODE_HEAT_COOL,
}

HVAC_MODES = {
    LYRIC_HVAC_MODE_OFF: HVAC_MODE_OFF,
    LYRIC_HVAC_MODE_HEAT: HVAC_MODE_HEAT,
    LYRIC_HVAC_MODE_COOL: HVAC_MODE_COOL,
    LYRIC_HVAC_MODE_HEAT_COOL: HVAC_MODE_HEAT_COOL,
}

HVAC_ACTIONS = {
    LYRIC_HVAC_ACTION_OFF: CURRENT_HVAC_OFF,
    LYRIC_HVAC_ACTION_HEAT: CURRENT_HVAC_HEAT,
    LYRIC_HVAC_ACTION_COOL: CURRENT_HVAC_COOL,
}

SERVICE_HOLD_TIME = "set_hold_time"
ATTR_TIME_PERIOD = "time_period"

SCHEMA_HOLD_TIME = {
    vol.Required(ATTR_TIME_PERIOD, default="01:00:00"): vol.All(
        cv.time_period,
        cv.positive_timedelta,
        lambda td: strftime("%H:%M:%S", localtime(time() + td.total_seconds())),
    )
}


async def async_setup_entry(
    hass: HomeAssistant, entry: ConfigEntry, async_add_entities
) -> None:
    """Set up the Honeywell Lyric climate platform based on a config entry."""
    coordinator: DataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]

    entities = []

    for location in coordinator.data.locations:
        for device in location.devices:
            entities.append(
                LyricClimate(
                    coordinator, location, device, hass.config.units.temperature_unit
                )
            )

    async_add_entities(entities, True)

    platform = entity_platform.async_get_current_platform()

    platform.async_register_entity_service(
        SERVICE_HOLD_TIME,
        SCHEMA_HOLD_TIME,
        "async_set_hold_time",
    )


class LyricClimate(LyricDeviceEntity, ClimateEntity):
    """Defines a Honeywell Lyric climate entity."""

    def __init__(
        self,
        coordinator: DataUpdateCoordinator,
        location: LyricLocation,
        device: LyricDevice,
        temperature_unit: str,
    ) -> None:
        """Initialize Honeywell Lyric climate entity."""
        self._temperature_unit = temperature_unit

        # Setup supported hvac modes
        self._hvac_modes = [HVAC_MODE_OFF]

        # Add supported lyric thermostat features
        if LYRIC_HVAC_MODE_HEAT in device.allowedModes:
            self._hvac_modes.append(HVAC_MODE_HEAT)

        if LYRIC_HVAC_MODE_COOL in device.allowedModes:
            self._hvac_modes.append(HVAC_MODE_COOL)

        if (
            LYRIC_HVAC_MODE_HEAT in device.allowedModes
            and LYRIC_HVAC_MODE_COOL in device.allowedModes
        ):
            self._hvac_modes.append(HVAC_MODE_HEAT_COOL)

        super().__init__(
            coordinator,
            location,
            device,
            f"{device.macID}_thermostat",
            device.name,
            None,
        )

    @property
    def supported_features(self) -> int:
        """Return the list of supported features."""
        return SUPPORT_FLAGS

    @property
    def temperature_unit(self) -> str:
        """Return the unit of measurement."""
        return self._temperature_unit

    @property
    def current_temperature(self) -> float | None:
        """Return the current temperature."""
        return self.device.indoorTemperature

    @property
    def hvac_action(self) -> str:
        """Return the current hvac action."""
        action = HVAC_ACTIONS.get(self.device.operationStatus.mode, None)
        if action == CURRENT_HVAC_OFF and self.hvac_mode != HVAC_MODE_OFF:
            action = CURRENT_HVAC_IDLE
        return action

    @property
    def hvac_mode(self) -> str:
        """Return the hvac mode."""
        return HVAC_MODES[self.device.changeableValues.mode]

    @property
    def hvac_modes(self) -> list[str]:
        """List of available hvac modes."""
        return self._hvac_modes

    @property
    def target_temperature(self) -> float | None:
        """Return the temperature we try to reach."""
        device = self.device
        if not device.hasDualSetpointStatus:
            return device.changeableValues.heatSetpoint
        return None

    @property
    def target_temperature_low(self) -> float | None:
        """Return the upper bound temperature we try to reach."""
        device = self.device
        if device.hasDualSetpointStatus:
            return device.changeableValues.coolSetpoint
        return None

    @property
    def target_temperature_high(self) -> float | None:
        """Return the upper bound temperature we try to reach."""
        device = self.device
        if device.hasDualSetpointStatus:
            return device.changeableValues.heatSetpoint
        return None

    @property
    def preset_mode(self) -> str | None:
        """Return current preset mode."""
        return self.device.changeableValues.thermostatSetpointStatus

    @property
    def preset_modes(self) -> list[str] | None:
        """Return preset modes."""
        return [
            PRESET_NO_HOLD,
            PRESET_HOLD_UNTIL,
            PRESET_PERMANENT_HOLD,
            PRESET_TEMPORARY_HOLD,
            PRESET_VACATION_HOLD,
        ]

    @property
    def min_temp(self) -> float:
        """Identify min_temp in Lyric API or defaults if not available."""
        device = self.device
        if LYRIC_HVAC_MODE_COOL in device.allowedModes:
            return device.minCoolSetpoint
        return device.minHeatSetpoint

    @property
    def max_temp(self) -> float:
        """Identify max_temp in Lyric API or defaults if not available."""
        device = self.device
        if LYRIC_HVAC_MODE_HEAT in device.allowedModes:
            return device.maxHeatSetpoint
        return device.maxCoolSetpoint

    async def async_set_temperature(self, **kwargs) -> None:
        """Set new target temperature."""
        target_temp_low = kwargs.get(ATTR_TARGET_TEMP_LOW)
        target_temp_high = kwargs.get(ATTR_TARGET_TEMP_HIGH)

        device = self.device
        if device.hasDualSetpointStatus:
            if target_temp_low is None or target_temp_high is None:
                raise HomeAssistantError(
                    "Could not find target_temp_low and/or target_temp_high in arguments"
                )
            _LOGGER.debug("Set temperature: %s - %s", target_temp_low, target_temp_high)
            try:
                await self._update_thermostat(
                    self.location,
                    device,
                    coolSetpoint=target_temp_low,
                    heatSetpoint=target_temp_high,
                )
            except LYRIC_EXCEPTIONS as exception:
                _LOGGER.error(exception)
        else:
            temp = kwargs.get(ATTR_TEMPERATURE)
            _LOGGER.debug("Set temperature: %s", temp)
            try:
                await self._update_thermostat(self.location, device, heatSetpoint=temp)
            except LYRIC_EXCEPTIONS as exception:
                _LOGGER.error(exception)
        await self.coordinator.async_refresh()

    async def async_set_hvac_mode(self, hvac_mode: str) -> None:
        """Set hvac mode."""
        _LOGGER.debug("Set hvac mode: %s", hvac_mode)
        try:
            await self._update_thermostat(
                self.location, self.device, mode=LYRIC_HVAC_MODES[hvac_mode]
            )
        except LYRIC_EXCEPTIONS as exception:
            _LOGGER.error(exception)
        await self.coordinator.async_refresh()

    async def async_set_preset_mode(self, preset_mode: str) -> None:
        """Set preset (PermanentHold, HoldUntil, NoHold, VacationHold) mode."""
        _LOGGER.debug("Set preset mode: %s", preset_mode)
        try:
            await self._update_thermostat(
                self.location, self.device, thermostatSetpointStatus=preset_mode
            )
        except LYRIC_EXCEPTIONS as exception:
            _LOGGER.error(exception)
        await self.coordinator.async_refresh()

    async def async_set_hold_time(self, time_period: str) -> None:
        """Set the time to hold until."""
        _LOGGER.debug("set_hold_time: %s", time_period)
        try:
            await self._update_thermostat(
                self.location,
                self.device,
                thermostatSetpointStatus=PRESET_HOLD_UNTIL,
                nextPeriodTime=time_period,
            )
        except LYRIC_EXCEPTIONS as exception:
            _LOGGER.error(exception)
        await self.coordinator.async_refresh()
